TI_GDT equ 0
RPL0 equ 0
; 视频选择子:除了后三位前面字段位选择子的索引后面为全局gdt和0特权级RPL
SELECTOR_VIDOE equ (0x0003<<3) + TI_GDT + RPL0

[bits 32]

section .text

;--------------  put_char  ---------------
; 把栈中的1个字符写入光标所在处
;-----------------------------------------

;对外描述接口
global put_char
put_char:
    pushad      ;备份寄存器环境
    mov ax, SELECTOR_VIDOE ; 不能把立即数输入段寄存器
    mov gs, ax

    mov dx, 0x03d4 ;索引端口 address register
    mov al, 0x0e ;用于提供光标位置的高八位 0x0e索引提供光标位置高八位
    out dx, al ;发送索引到索引端口
    mov dx, 0x03d5 ;通过读写端口0x3d5获取光标位置 数据端口 data register
    in al, dx ;从数据端口输入数据
    mov ah, al ;将高八位保存到ah

    mov dx, 0x03d4 ; 索引端口
    mov al, 0x0f ; 低八位索引
    out dx, al ; 发送索引
    mov dx, 0x03d5 ; 数据端口
    in al, dx ; 写入第八位到al

    mov bx, ax ; 光标数据写入bx

    mov ecx, [esp + 36] ; pushad 压入32字节，加上保存的返回地址4字节共36字节这里在取出第一个参数
    
    ; 比较字符各种控制符然后调到相应的处理函数里面
    cmp cl, 0xd
    jz .is_carriage_return
    cmp cl, 0xa
    jz .is_line_feed

    cmp cl, 0x8
    jz .is_backspace
    jmp .put_other

;-----------------------------------------关于光标---------------------------------------------
; 80x25字符格式下坐标是线性地址,每个字符占两字节，高字节为背景色和前景色低字节为ascii字符编码
; 所以表示能表示80x25 = 2000个字符，则显存偏移量上限为: 2000字符 x 2字节 = 4000 字节
; 所以要把光标地址映射为显存地址就要：光标地址 x 2 = 显存地址
; 所以获取到光标地址要 shl bx, 1 左移一位代表乘2
;----------------------------------------------------------------------------------------------

;--------------处理回退符--------------
; bx 为坐标位置
;--------------------------------------
.is_backspace:
    dec bx,    ; 对于光标坐标后移一位
    shl bx, 1  ; 将光标左标映射到显存地址偏移量
    mov byte[gs:bx], 0x20 ; 旧字符的ascii填为空白
    inc bx  
    mov byte [gs:bx], 0x07 ; 设置为黑底白色
    shr bx, 1  ; 恢复为逻辑坐标
    jmp .set_cursor

;-------------处理其他字符------------
; bx 为坐标位置
; cl 为字符ascii
;-------------------------------------
.put_other:
    shl bx, 1 ; 映射到显存地址

    mov [gs:bx], cl ; 写入ascii到显存地址
    inc bx 
    mov byte [gs:bx], 0x07 ; 白底黑色
    shr bx, 1 ; 恢复逻辑坐标
    inc bx ; 逻辑坐标加一为新光标位置
    cmp bx, 2000 ; 如果逻辑坐标到达最后一位不设置光标
    jl .set_cursor ; 设置光标

;-----------处理换行逻辑-------------
.is_line_feed:
.is_carriage_return:
; 当前光标位置bx/80 商为ax,余数为dx
; 计算下行位置公式  (此行坐标 % 80 ) - ( 此行坐标 mod 80) + 80
    xor dx, dx ; 清空dx
    mov ax, bx ; 将被除数写入ax
    mov si, 80 ; 除数80(一行字符数)

    div si ; 对于坐标取模和商

    sub bx, dx

.is_carriage_return_end:
    add bx, 80  
    cmp bx, 2000 ; 如果不是行末直接去设置光标

.is_line_feed_end:
    jl .set_cursor

;------------滚动页面--------------
.roll_screen:
    cld
    mov ecx, 960 ; (2000 - 80) 1920个字符对应3840字节,每次搬4字节 3840 / 4 = 960次

    mov esi, 0xc00b80a0 ; 搬字符从第一行搬到第0行
    mov edi, 0xc00b8000
    rep movsd

    mov ebx, 3840 ; 最后一行的偏移地址
    mov ecx, 80 ; 清楚80个字符循环80次

;------------最后一行清除------------
;------------------------------------

.cls:
    mov word [gs:ebx], 0x0720 ; 白底黑字空格
    add ebx, 2 ; 加两字节
    loop .cls ; 直到80次结束
    mov bx, 1920 ; 设置光标位置bx为最后一行开头

;-----------设置光标--------------
.set_cursor:
    mov dx, 0x03d4
    mov al, 0x0e
    out dx, al
    mov dx, 0x03d5
    mov al, bh
    out dx, al

    mov dx, 0x03d4
    mov al, 0x0f
    out dx, al

    mov dx, 0x03d4
    mov al, 0x0f
    out dx, al
    mov dx, 0x03d5
    mov al, bl
    out dx, al

; 结束函数返回
.put_char_done:
    popad
    ; 恢复寄存器
    ret

[bits 32]
section .text
global put_str
put_str:
     push ebx
     push ecx
     xor ecx, ecx
     mov ebx, [esp + 12]
.goon:
     mov cl, [ebx]
     cmp cl, 0
     jz .str_over
     push ecx
     call put_char
     add esp, 4
     inc ebx
     jmp .goon
.str_over:
     pop ecx
     pop ebx
     ret

section .data:
     put_int_buffer dq 0

section .text:
global put_int
put_int:
     pushad
     mov ebp, esp
     mov eax, [ebp + 36]
     mov edx, eax
     mov edi, 7 ; 缓冲区起始偏移量
     mov ecx, 8 ; 循环次数8次
     mov ebx, put_int_buffer 
     push '0'
     call put_char
     add esp, 4
     push 'x'
     call put_char
     add esp, 4
     .hex_based_4bits:
          and edx, 0x0000000F ; 因为16进制四位代表一位所以与操作取其最后四位
	  cmp edx, 9    ; 如果这个数字大于9则去转化为16进制的字符串
	  jg .is_A2F    ; is_A2F 处理 a-f的数字
	  add edx, '0' ; 对于0这个基本地址加上二进制数据就变成了字符串
	  jmp .store ; 去存储缓冲区
     .is_A2F:
     	  sub edx, 10  ; 对于大于9的数字对与10减去然后就得到了距离A字符的偏移量
	  add edx, 'A' ; 我们直接相加这个偏移量就能得到对应字符
     .store:
          mov [ebx + edi], dl ; 将转化好的字符写入
	  dec edi ; 减少偏移量
	  shr eax, 4 ; 处理好了左移四位
	  mov edx, eax ; 写回edx
	  loop .hex_based_4bits ; 重复执行直到

     .ready_to_print:
          inc edi
     .skip_prefix_0:
          cmp edi, 8

	  je .full0

     .go_on_skip:
          mov cl, [put_int_buffer + edi]
	  inc edi
	  cmp cl, '0'
	  je .skip_prefix_0

	  dec edi
	  jmp .put_each_num

     .full0:
          mov cl, '0'

     .put_each_num:
          push ecx 
	  call put_char
	  add esp, 4
	  inc edi
	  mov cl, [put_int_buffer + edi]
	  cmp edi, 8
	  jl .put_each_num
	  popad
	  ret
